package nl.stroep.classbuilder.cb 
{
	import flash.events.EventDispatcher;
	import flash.events.IEventDispatcher;
	import flash.utils.ByteArray;
	import nl.stroep.classbuilder.cb.interfaces.ICbObject;
	import nl.stroep.classbuilder.cb.interfaces.ICbWrapper;
	import nl.stroep.classbuilder.definitions.AccessModifiers;
	import nl.stroep.classbuilder.definitions.AS3ClassDefinition;
	import nl.stroep.classbuilder.definitions.AS3Classes;
	import nl.stroep.classbuilder.definitions.Attributes;
	import nl.stroep.classbuilder.definitions.Chars;
	import nl.stroep.classbuilder.definitions.ClassModifier;
	import nl.stroep.classbuilder.definitions.DataTypes;
	import nl.stroep.classbuilder.templates.Templates;
	import nl.stroep.classbuilder.templates.TemplateVariableDefinitions;
	import nl.stroep.classbuilder.utils.ObjectUtil;
	import nl.stroep.classbuilder.utils.StringUtil;
	import nl.stroep.classbuilder.utils.XMLUtil;
	
	/**
	 * Defines a class template, which lets you add methods and properties that you define.
	 * @author Mark Knol
	 */
	[bindable]
	public class CbClass extends EventDispatcher implements ICbWrapper, IEventDispatcher
	{		
		private var _name:String;
		private var _type:String;
		private var _defaultValue:String; 
		public var functions:Vector.<CbMethod>;
		public var variables:Vector.<CbVariable>;
		public var constructorParams:Vector.<CbParameter>;
		public var isFinal:Boolean;
		public var implementsClass:String;
		public var packAge:String;
		public var author:String;
		public var classModifier:String;
		
		private var currentImports:Vector.<AS3ClassDefinition> = new Vector.<AS3ClassDefinition>();
		
		/**
		 * 
		 * @param	name				Required. Name of the CbClass instance
		 * @param	type				Optional. Defines a class that is a subclass of another class (extends)
		 * @param	implementsClass		Optional. Specifies that a class implements one or more interfaces
		 * @param	classModifier		Optional. Use statics from nl.stroep.classbuilder.definitions.ClassModifier
		 * @param	packAge				Optional. Allows you to organize your code into discrete groups that can be imported by other scripts.
		 * @param	author				Optional. Creator of the class.
		 */
		public function CbClass(name:String, type:String = null, implementsClass:String = null, classModifier:String = ClassModifier.PUBLIC, packAge:String = null, author:String = null):void 
		{ 
			this._name = name;
			this._type = type;
			this._defaultValue = "// TODO: Class _defaultValue";
			
			this.functions = new Vector.<CbMethod>();
			this.variables = new Vector.<CbVariable>();
			this.constructorParams = new Vector.<CbParameter>();
			
			this.implementsClass = implementsClass;
			this.classModifier = classModifier;
			this.packAge = packAge;
			this.author = author;
		}
		
		public function addMethod(cbMethod:CbMethod):void
		{
			functions.push(cbMethod);
		}
		
		public function addVariable(cbVariable:CbVariable):void
		{
			variables.push(cbVariable);
		}
		
		/// gets formatted class using template for output 
		public override function toString( ):String 
		{
			var retval:String = Templates.getInstance().classTemplate;

			retval = StringUtil.replace(retval, TemplateVariableDefinitions.VARIABLES, getVariablesFromConstructor() + Chars.NEWLINE + getVariables());
			retval = StringUtil.replace(retval, TemplateVariableDefinitions.FUNCTIONS, getFunctions());
			retval = StringUtil.replace(retval, TemplateVariableDefinitions.GETTERS_SETTTERS, getGetterAndSetters());
			retval = StringUtil.replace(retval, TemplateVariableDefinitions.CONSTRUCTOR, getConstructor());
			retval = StringUtil.replace(retval, TemplateVariableDefinitions.CLASS_NAME, name);
			retval = StringUtil.replace(retval, TemplateVariableDefinitions.PACKAGE, packAge ? packAge : Chars.EMPTY);
			retval = StringUtil.replace(retval, TemplateVariableDefinitions.AUTHOR, author ? author : "defaultUser");
			retval = StringUtil.replace(retval, TemplateVariableDefinitions.EXTENDS, _type ? Attributes.EXTENDS + Chars.SPACE + _type : Chars.EMPTY);
			retval = StringUtil.replace(retval, TemplateVariableDefinitions.IMPLEMENTS, implementsClass ? Attributes.IMPLEMENTS + Chars.SPACE + implementsClass : Chars.EMPTY);
			retval = StringUtil.replace(retval, TemplateVariableDefinitions.CLASS_MODIFIER, isFinal ? ClassModifier.FINAL : Chars.EMPTY);
			retval = StringUtil.replace(retval, TemplateVariableDefinitions.IMPORT_STATEMENTS, getImports());
			retval = StringUtil.replace(retval, TemplateVariableDefinitions.ACCESS_MODIFIER, AccessModifiers.PUBLIC);
			retval = StringUtil.replace(retval, TemplateVariableDefinitions.MODIFIER, classModifier ? classModifier : Chars.EMPTY);
						
			return retval;
		}
		
		/// Serialize variables from cbClass to xml
		public function toXML():XML
		{
			var xml:XML = <cbClass/>;
			
			xml.appendChild( XMLUtil.getNode("name", name) );
			xml.appendChild( XMLUtil.getNode("type", type) );
			xml.appendChild( XMLUtil.getNode("implementsClass", implementsClass) );
			xml.appendChild( XMLUtil.getNode("packAge", packAge) );
			xml.appendChild( XMLUtil.getNode("author", author) );
			xml.appendChild( XMLUtil.getNode("classModifier", classModifier) );
			
			xml.appendChild(<methods/>);
			xml.appendChild(<variables/>);
			xml.appendChild(<constructorParams/>);
			
			var i:int = 0
			for (i = 0; i < functions.length; ++i) 
			{
				xml.methods.appendChild( functions[i].toXML() )
			}
			for (i = 0; i < variables.length; ++i) 
			{
				xml.variables.appendChild( variables[i].toXML() )
			}
			for (i = 0; i < constructorParams.length; ++i) 
			{
				xml.constructorParams.appendChild( constructorParams[i].toXML() )
			}
			
			return xml;
		}
		
		
		/// Creates vector with classes from xml
		public static function parseData(xml:XMLList):Vector.<CbClass>
		{
			var retval:Vector.<CbClass> = new Vector.<CbClass>()
			
			const total:uint = xml.cbClass.length()
			trace("class total", total)
			
			for (var i:int = 0; i < total; ++i) 
			{
				var item:XML = xml.cbClass[i];
				
				var cbClass:CbClass = new CbClass( item.name.text() );
				
				cbClass.functions 			= CbMethod.parseData( item.methods );
				cbClass.variables 			= CbVariable.parseData( item.variables );
				cbClass.constructorParams 	= CbParameter.parseData( item.constructorParams );
				
				cbClass.name 				= ObjectUtil.parseString( item.name.text() );
				cbClass.type 				= ObjectUtil.parseString( item.type.text() );
				cbClass.type 				= ObjectUtil.parseString( item.type.text() );
				cbClass.implementsClass		= ObjectUtil.parseString( item.implementsClass.text() );
				cbClass.packAge 			= ObjectUtil.parseString( item.packAge.text() );
				cbClass.author 				= ObjectUtil.parseString( item.author.text() );
				cbClass.classModifier 		= ObjectUtil.parseString( item.classModifier.text() );
				
				retval.push(cbClass);
			}
			
			return retval;
		}
		
		private function getGetterAndSetters():String
		{
			var retval:String = Chars.EMPTY;
			
			if (variables && variables.length > 0)
			{
				const TOTAL:int = variables.length;
				
				for (var i:uint = 0; i < TOTAL; ++i) 
				{
					var variable:CbVariable = variables[i];
					
					if (variable.isGetter)
					{
						var getter:CbGetMethod = new CbGetMethod(variable.name, variable.type);
						retval += getter.toString();
						retval += Chars.NEWLINE;
					}
					if (variable.isSetter)
					{
						var setter:CbSetMethod = new CbSetMethod(variable.name, variable.type);
						retval += setter.toString();
						retval += Chars.NEWLINE;
					}
				}
			}
			return retval;
		}
		
		private function getImports():String
		{
			var retval:String = Chars.EMPTY;
			
			var classes:AS3Classes = new AS3Classes();
			
			currentImports = new Vector.<AS3ClassDefinition>()
			
			if (_type)
			{
				var extendDefinition:AS3ClassDefinition = getImport( _type, classes );
				
				if (extendDefinition)
				{
					retval += Attributes.IMPORT + extendDefinition.packageName + Chars.DOT + extendDefinition.className + Chars.SEMICOLON + Chars.NEWLINE;
				}
			}
			
			if (implementsClass)
			{
				var implementsDefinition:AS3ClassDefinition = getImport( implementsClass, classes );		
				if (implementsDefinition)
				{
					retval += Attributes.IMPORT + implementsDefinition.packageName + Chars.DOT + implementsDefinition.className + Chars.SEMICOLON + Chars.NEWLINE;
				}
			}
			
			if (constructorParams && constructorParams.length > 0)
			{
				for (var i:uint = 0; i < constructorParams.length; ++i) 
				{
					var definition:AS3ClassDefinition = getImport( constructorParams[i].type, classes );
					
					if (definition)
					{
						retval += Attributes.IMPORT + definition.packageName + Chars.DOT + definition.className + Chars.SEMICOLON + Chars.NEWLINE;
					}
				}
			}
			return retval;
		}
		
		private function getImport( className:String, classes:AS3Classes ):AS3ClassDefinition
		{
			for (var i:uint = 0; i < classes.list.length; ++i)
			{
				var definition:AS3ClassDefinition = classes.list[i];
				
				if (currentImports.indexOf(definition) == -1)
				{
					if ( className.toLowerCase() == definition.className.toLowerCase() )
					{
						if (definition.packageName != Chars.EMPTY)
						{
							currentImports.push(definition);
							return definition;
						}
					}
				}
			}
			return null;
		}
		
		private function getConstructor():String
		{
			var constructor:CbMethod = new CbMethod(name, DataTypes.VOID, constructorParams, AccessModifiers.PUBLIC, null, "// TODO: Constructor " + name);
			return constructor.toString();
		}
		
		private function getFunctions():String
		{
			var retval:String = Chars.EMPTY;
			
			if (functions && functions.length > 0)
			{
				const TOTAL:int = functions.length;
				
				for (var i:uint = 0; i < functions.length; ++i) 
				{
					var func:CbMethod = functions[i];
					retval += func.toString();
					retval += Chars.NEWLINE;
				}
			}
			return retval;
		}
		
		private function getVariablesFromConstructor():String
		{
			var retval:String = Chars.EMPTY;
			
			if (constructorParams && constructorParams.length > 0)
			{
				const TOTAL:int = constructorParams.length;
				
				for (var i:uint = 0; i < constructorParams.length; ++i) 
				{
					var constructorParam:CbParameter = constructorParams[i];
					var variable:CbVariable = new CbVariable(constructorParam.name, constructorParam.type);
					
					if (constructorParam.addToClass)
					{
						retval += variable.toString();
						retval += Chars.NEWLINE;
					}
				}
			}
			return retval;
		}
		
		private function getVariables():String
		{
			var retval:String = Chars.EMPTY;
			
			if (variables && variables.length > 0)
			{
				const TOTAL:int = variables.length;
				
				for (var i:uint = 0; i < TOTAL; ++i) 
				{
					var variable:CbVariable = variables[i];
					retval += variable.toString();
					retval += Chars.NEWLINE;
				}
			}
			return retval;
		}
		
		public function get name():String { return _name; }
		
		public function set name(value:String):void 
		{
			_name = value;
		}
		
		public function get type():String { return _type; }
		
		public function set type(value:String):void 
		{
			_type = value;
		}
		
		public function get defaultValue():String { return _defaultValue; }
		
		public function set defaultValue(value:String):void 
		{
			_defaultValue = value;
		}
		
	}

}